/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cz.mzk.manuscripts.sourceData.teiLinksCorrection;

import cz.mzk.rehan.commons.Dom4jUtils;
import java.io.File;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.dom4j.Attribute;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.XPath;
import rukopisyimport.sourceData.TeiDocument;

/**
 *
 * @author Martin Řehánek (rehan at mzk.cz)
 */
public class TeiToRepair {

    protected static final Map<String, String> prefixNamespaceMap = new HashMap<String, String>();
    private static final XPath pageFromXpath = DocumentHelper.createXPath("./@from");
    private static final XPath pageToXpath = DocumentHelper.createXPath("./@to");
    private static final XPath targetXpath = DocumentHelper.createXPath("./@target");
    private static final XPath locusXpath = DocumentHelper.createXPath(".//tei:locus");
    private static final XPath globaLocusXpath = DocumentHelper.createXPath("//tei:locus");
    private static final XPath globalSurfaceXpath = DocumentHelper.createXPath("//tei:surface");
    private static final XPath labelFromSurfaceXPath = DocumentHelper.createXPath("./tei:desc/tei:label");
    private static String[] idPrefices = {"id"};
    private final List<Node> locusesInMsItems = new ArrayList<Node>();
    private Map<Integer, List<Node>> numberSurfaceListMap = null;
    private final File srcFile;
    private final File destFile;
    private TeiDocument tei;
    private final PrintStream out;

    static {
        prefixNamespaceMap.put("tei", "http://www.tei-c.org/ns/1.0");
        pageFromXpath.setNamespaceURIs(prefixNamespaceMap);
        pageToXpath.setNamespaceURIs(prefixNamespaceMap);
        locusXpath.setNamespaceURIs(prefixNamespaceMap);
        globaLocusXpath.setNamespaceURIs(prefixNamespaceMap);
        targetXpath.setNamespaceURIs(prefixNamespaceMap);
        globalSurfaceXpath.setNamespaceURIs(prefixNamespaceMap);
        labelFromSurfaceXPath.setNamespaceURIs(prefixNamespaceMap);
    }

    TeiToRepair(File srcFile, PrintStream out) {
        this.srcFile = srcFile;
        //this.destFile = new File(srcFile.getAbsolutePath().concat(".corr"));
        this.destFile = srcFile;
        this.out = out;
    }

    public void repair() {
        try {
            tei = new TeiDocument(srcFile);
            List<Node> msItems = tei.findMsItems();
            out.println("počet elementů msItem:" + msItems.size());
            int msItemCounter = 0;
            for (Node msItem : msItems) {
                out.println();
                repairMsItem(msItem, ++msItemCounter);
            }
            repairLocusesOutsideMsItems();
            Dom4jUtils.saveDocument(tei.getDocument(), destFile);
        } catch (Exception ex) {
            out.println("CHYBA: " + ex.getMessage());
        }
    }

    private void repairMsItem(Node msItem, int counter) {
        out.println(counter + ". msItem:");
        List<Node> locusList = locusXpath.selectNodes(msItem);
        for (Node locus : locusList) {
            out.println("----------------");
            locusesInMsItems.add(locus);
            try {
                repairLocus((Element) locus);
            } catch (Exception e) {
                out.println("CHYBA: " + e.getMessage());
            }
        }
        out.println("----------------");
    }

    private void repairLocus(Element locus) throws Exception {
        String description = null;
        if (locus == null) {
            description = "BEZ_TEXTU_V_LOCUS";
        } else {
            description = locus.getText();
        }
        out.println("locus " + description + ": ");
        if (hasFromAndTo(locus)) {
            updateFromIfPossible(locus);
            updateToIfPossible(locus);
        } else {
            Attribute target = findTarget(locus);
            updateLink(target);
        }
    }

    private Attribute findTarget(Element locus) {
        Attribute target = (Attribute) targetXpath.selectSingleNode(locus);
        if (target == null) {
            target = DocumentHelper.createAttribute(locus, "target", locus.getTextTrim());
            locus.add(target);
        }
        return target;
    }

    private boolean hasFromAndTo(Node locus) throws Exception {
        Attribute from = getFromAttr(locus);
        Attribute to = getToAttr(locus);
        if (from != null && to != null) {
            return true;
        } else if (from == null && to == null) {
            return false;
        } else if (from == null) {
            throw new Exception("'to' přítomen ale 'from' chybi");
        } else {
            throw new Exception("'from' přítomen ale 'to' chybi");
        }
    }

    private void updateFromIfPossible(Node msItem) throws Exception {
        try {
            out.println("from:");
            Attribute fromAttr = getFromAttr(msItem);
            updateLink(fromAttr);
        } catch (Exception ex) {
            out.println("CHYBA: " + ex.getMessage());
        }
    }

    private void updateLink(Attribute attribute) throws Exception {
        String originalValue = attribute.getText();
        if (!hasIdPrefix(originalValue)) {
            Node surface = findSurfaceByLabel(originalValue);
            String newValue = findId(surface);
            updateAttribute(attribute, newValue);
            out.println("'" + originalValue + "' -> '" + newValue + "'");
        }
    }

    private void updateToIfPossible(Node msItem) throws Exception {
        try {
            out.println("to:");
            Attribute toAttr = getToAttr(msItem);
            updateLink(toAttr);
        } catch (Exception ex) {
            out.println("CHYBA: " + ex.getMessage());
        }
    }

    private boolean hasIdPrefix(String string) {
        for (int i = 0; i < idPrefices.length; i++) {
            if (string.startsWith(idPrefices[i])) {
                return true;
            }
        }
        return false;
    }

    private Attribute getFromAttr(Node msItem) {
        return getRangeAttr(msItem, pageFromXpath);
    }

    private Attribute getToAttr(Node msItem) {
        return getRangeAttr(msItem, pageToXpath);
    }

    private Attribute getRangeAttr(Node msItem, XPath xpath) {
        return (Attribute) xpath.selectSingleNode(msItem);
    }

    private Node findSurfaceByLabel(String originalFrom) throws Exception {
        Node exactMatch = findSurfaceByExactLabelMatch(originalFrom);
        if (exactMatch != null) {
            return exactMatch;
        } else {
            Node possibleMatch = findSurfaceByNumberMatch(originalFrom);
            if (possibleMatch != null) {
                return possibleMatch;
            } else {
                throw new Exception("nenalezen element surface s labelem \"" + originalFrom + "\" (nebo ekvivalentním)");
            }
        }
    }

    private Node findSurfaceByExactLabelMatch(String originalFrom) throws Exception {
        XPath xpath = DocumentHelper.createXPath("//tei:surface/tei:desc/tei:label[. = '" + originalFrom + "']/../..");
        xpath.setNamespaceURIs(prefixNamespaceMap);
        List nodes = xpath.selectNodes(tei.getDocument());
        if (nodes.isEmpty()) {
            return null;
        } else if (nodes.size() > 1) {
            throw new Exception("nalezeno více (" + nodes.size() + ") elementů surface s labelem " + originalFrom);
        } else {
            return (Node) nodes.get(0);
        }
    }

    private Node findSurfaceByNumberMatch(String originalFrom) throws Exception {
        try {
            int originalNumber = Integer.parseInt(originalFrom);
            Map<Integer, List<Node>> numberSurfaceMap = getNumberSurfaceListMap();
            List<Node> surfaceNodes = numberSurfaceMap.get(Integer.valueOf(originalNumber));
            if (surfaceNodes == null) {
                System.err.println("surfaceNodes == null for " + originalNumber);
                return null;
            } else {
                switch (surfaceNodes.size()) {
                    case 0:
                        System.err.println("surfaceNodes.size == 0");
                        return null;
                    case 1:
                        return surfaceNodes.get(0);
                    default:
                        //TODO: pripsat obsahy labelu
                        throw new Exception("nalezeno více (" + surfaceNodes.size() + ") elementů surface s labelem ekvivalentním '" + originalFrom + "'");
                }
            }
        } catch (NumberFormatException e) {
            return null;
        }
    }

    private Map<Integer, List<Node>> getNumberSurfaceListMap() {
        if (numberSurfaceListMap == null) {
            numberSurfaceListMap = createNumberSurfaceListMap();
        }
        return numberSurfaceListMap;
    }

    private Map<Integer, List<Node>> createNumberSurfaceListMap() {
        Map<Integer, List<Node>> result = new HashMap<Integer, List<Node>>();
        List<Node> surfaceNodes = globalSurfaceXpath.selectNodes(tei.getDocument());
        for (Node surface : surfaceNodes) {
            Node label = labelFromSurfaceXPath.selectSingleNode(surface);
            addLabelToMapIfPossible(label, surface, result);
        }
        System.out.println("");
        return result;
    }

    private void addLabelToMapIfPossible(Node label, Node surface, Map<Integer, List<Node>> result) {
        if (label != null) {
            String labelStr = label.getText();
            try {
                Integer labelInt = Integer.valueOf(labelStr);
                addToNumberSurfaceListMap(labelInt, surface, result);
            } catch (NumberFormatException e) {
                //do nothing
            }
        }
    }

    private void addToNumberSurfaceListMap(Integer pageNumber, Node surface, Map<Integer, List<Node>> numberSurfacListMap) {
        List<Node> surfaceList = numberSurfacListMap.get(pageNumber);
        if (surfaceList == null) {
            surfaceList = new ArrayList<Node>();
            numberSurfacListMap.put(pageNumber, surfaceList);
        }
        surfaceList.add(surface);
    }

    private String findId(Node surface) throws Exception {
        XPath xpath = DocumentHelper.createXPath("./@xml:id");
        xpath.setNamespaceURIs(prefixNamespaceMap);
        Node node = xpath.selectSingleNode(surface);
        if (node == null) {
            throw new Exception("nenalezen atribut surface/@xml:id");
        } else {
            return node.getText();
        }
    }

    private void updateAttribute(Attribute attribute, String newValue) {
        attribute.setValue(newValue);
    }

    private void repairLocusesOutsideMsItems() {
        List<Node> locusesOutsideMsItems = findLocusesOutsideMsItems();
        if (!locusesOutsideMsItems.isEmpty()) {
            out.println();
            out.println("elementy locus mimo msItem:");
            for (Node locus : locusesOutsideMsItems) {
                out.println("----------------");
                try {
                    locusesInMsItems.add(locus);
                    repairLocus((Element) locus);
                } catch (Exception e) {
                    out.println("CHYBA: " + e.getMessage());
                }
            }
            out.println("----------------");
        }
    }

    private List<Node> findLocusesOutsideMsItems() {
        List<Node> allNodes = globaLocusXpath.selectNodes(tei.getDocument());
        List<Node> result = new ArrayList<Node>();
        for (Node node : allNodes) {
            if (!locusesInMsItems.contains(node)) {
                result.add(node);
            }
        }
        return result;
    }
}
